package CommentApi;

import CommentApi.Comments.ClassComment;
import CommentApi.Comments.MethodComment;
import CommentApi.Output.CommentOutput;
import CommentApi.Output.ControllerInfo;
import com.sun.javadoc.*;
import lombok.SneakyThrows;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;


public class ControllerRootDocResolver {
    private Set<Class> noNeedParamClasses;
    private RootDoc rootDoc;
    public ControllerRootDocResolver(RootDoc rootDoc){
        
        this.rootDoc = rootDoc;
        noNeedParamClasses = new HashSet<>();
        noNeedParamClasses.addAll(Set.of(HttpServletResponse.class, HttpServletRequest.class, HttpSession.class));
    }

    public ControllerRootDocResolver(RootDoc rootDoc,Set<Class> noNeedParamClasses){
        this(rootDoc);
        this.noNeedParamClasses.addAll(noNeedParamClasses);
    }

    public void addNoNeedParamClasses(Class aclass){
        noNeedParamClasses.add(aclass);
    }

    public List<ControllerInfo> resolver(){
        List<ControllerInfo> list = new ArrayList<>();
        for (ClassDoc aClass : rootDoc.classes()) {
            final ClassComment classComment = transToClassComment(aClass);
            if (!isExistAnnotation(classComment.getAClass(), Controller.class)){
                continue;
            }
            final MethodDoc[] methodDocs = aClass.methods();

            checkMethodDocs(methodDocs);

            final ArrayList<MethodComment> methodComments = new ArrayList<>();

            for (MethodDoc methodDoc : methodDocs) {
                MethodComment comment = transToMethodComment(methodDoc, classComment.getAClass());
                if (comment != null) {
                    methodComments.add(comment);
                }
            }
            list.add(new ControllerInfo(classComment, methodComments));
        }
        return list;
    }

    @SneakyThrows
    private ClassComment transToClassComment(ClassDoc classDoc){

        try {
            return new ClassComment(Class.forName(classDoc.qualifiedTypeName()),classDoc.commentText());
        } catch (ClassNotFoundException e) {
            char[] chars = classDoc.qualifiedTypeName().toCharArray();
            int i = classDoc.qualifiedTypeName().lastIndexOf(".");
            chars[i] = '$';
            return new ClassComment(Class.forName(new String(chars)),classDoc.commentText());
        }

    }
    @SneakyThrows
    private MethodComment transToMethodComment(MethodDoc methodDoc,Class sourceClass){
        final Parameter[] parameters = methodDoc.parameters();
        final Class[] classes = new Class[parameters.length];
        for (int i = 0; i < classes.length; i++) {
            classes[i] = Class.forName(parameters[i].type().qualifiedTypeName());
        }
        final Method method = sourceClass.getMethod(methodDoc.name(), classes);
        if (!isExistAnnotation(method, RequestMapping.class)){
            return null;
        }
        final MethodComment.MethodCommentBuilder builder = MethodComment.builder();
        builder.method(method);

        builder.methodComment(methodDoc.commentText());

        final Map<String, String> paramComment = Arrays.stream(methodDoc.tags("@param"))
                .filter(t->checkParamTags(t,method))
                .map(this::transToNameAndComment)
                .collect(Collectors.toMap(s -> s[0], s -> s[1]));
        builder.paramComment(paramComment);

        builder.returnDesc(methodDoc.tags("@return")[0].text());

        final Tag[] otherDesc = methodDoc.tags("@otherDesc");
        if (otherDesc.length != 0) {
            builder.otherDesc(otherDesc[0].text());
        }

        return builder.build();
    }

    private String[] transToNameAndComment(Tag tag){
        final String[] strings = new String[2];
        final String text = tag.text();
        final int index = text.indexOf(" ");
        if (index==-1){
            throw new RuntimeException("检查"+tag.position().toString()+"的"+text+"标签是否书写正确");
        }
        strings[0] = text.substring(0, index);
        strings[1] = text.substring(index + 1).lines().map(String::trim)
                .collect(Collectors.joining("\n"));
        return strings;
    }

    // TODO: 2020/6/4 过滤合适的tag,比如说request参数没必要加入
    private boolean checkParamTags(Tag tag,Method method){
        for (java.lang.reflect.Parameter parameter : method.getParameters()) {
            if (parameter.getName().equals(transToNameAndComment(tag)[0])) {
                if (noNeedParamClasses.contains(parameter.getType())) {
                    return false;
                }
            }
        }
        return true;
    }

    private void checkMethodDocs(MethodDoc[] methodDocs){

        for (MethodDoc methodDoc : methodDocs) {
            //检测param标签
            final Tag[] paramTags = methodDoc.tags("@param");
            final Parameter[] parameters = methodDoc.parameters();
            if (parameters.length != paramTags.length){
                throw new RuntimeException(methodDoc.qualifiedName()+"注释中形参标签数目和实际函数形参数目不一致");
            }
            for (int i = 0; i < parameters.length; i++) {
                final String nameInComment = transToNameAndComment(paramTags[i])[0];
                final String nameInMethod = parameters[i].name();
                if (!nameInComment.equals(nameInMethod)){
                    throw new RuntimeException(
                            methodDoc.qualifiedName()+"注释中形参标签中参数名和实际函数中的参数名不一致,"
                            +"注释中为"+nameInComment
                            +"实际函数中为"+nameInMethod
                            +"\n请严格按照方法参数顺序书写标签顺序"
                    );
                }
            }
            //检测return标签
            if (methodDoc.tags("@return").length == 0) {
                throw new RuntimeException(methodDoc.qualifiedName()+"的返回值标签(@return)缺失");
            }
        }
    }

    private boolean isExistAnnotation(Class source, Class<? extends Annotation> target){
        return isExistAnnotation(source.getAnnotations(), target);
    }
    private boolean isExistAnnotation(Method source, Class<? extends Annotation> target){
        return isExistAnnotation(source.getAnnotations(), target);
    }

    private boolean isExistAnnotation(Annotation[] outerAnnotations,Class<? extends Annotation> target){
        HashSet<Annotation> hasChecked = new HashSet<>();
        Queue<Annotation> queue = new ArrayDeque<>();
        for (Annotation annotation : outerAnnotations) {
            queue.offer(annotation);
        }
        while (!queue.isEmpty()){
            Annotation poll = queue.poll();
            if (hasChecked.contains(poll)) {
                continue;
            }
            if (poll.annotationType().equals(target)) {
                return true;
            }
            hasChecked.add(poll);
            for (Annotation annotation : poll.annotationType().getAnnotations()) {
                queue.offer(annotation);
            }
        }
        return false;
    }



}
